from emmodels.em_base_data import BaseData
from emmongo import em_mongomanager as mongoManager
import datetime
from LSTM.variableset import EMVar
import pymongo
from emutils import time_utils
from dateutil.parser import parse
import tushare as ts
import pandas as pd
import numpy as np
from talib import abstract
day_cache = {}
min_cache = {}
index_date_cache = {}

class SelfFund(BaseData):

    SOURCE_STRATEGY_PREDICT = 'SOURCE_STRATEGY_PREDICT'

    def __init__(self, stkcode=None, stkname=None, last_update_time = datetime.datetime.now().strftime('%Y-%m-%d'), source='default', tag='tag'):
        self.stkcode = stkcode
        self.stkname = stkname
        self.score = 0
        self.last_update_time = last_update_time
        self.source = source
        self.tag = tag
        self.close = -1.0
        self.buy = 0
        self.sell = 0
        self.high = -1.0
        self.low = -1.0
        self.now = -1.0
        self.open = -1.0
        self.volume = -1.0
        self.__date = None

        self.jCache_turnover = 0
        self.jCache_now2close = 0
        self.jCache_now2high = 0
        self.jCache_now2low = 0
        self.jCache_open2close = 0

        self.__cache_day_data = None


    def set_base_data(self, basedata):
        d = basedata.__dict__
        self.set_up_with_dic(**d)

    def is_real_time_valid(self):
        return self.high != -1 and self.low !=-1 and self.open!= -1 and self.close!=-1 and self.volume!=-1 and self.now!=-1

    def update_from_ts(self):
        value = ts.get_realtime_quotes(self.stkcode)
        v_d = dict(value.ix[0,])
        self.open = float(v_d['open'])
        self.close = float(v_d['pre_close'])
        self.now = float(v_d['price'])
        self.low = float(v_d['low'])
        self.high = float(v_d['high'])
        self.volume = float(v_d['volume'])
        self.__date = v_d['date']
        self.caculate_cur_day()

    def caculate_cur_day(self):
        self.jCache_now2close = (self.now - self.close)/self.close
        self.jCache_now2high  = (self.now - self.high) / self.high
        self.jCache_now2low  = (self.now - self.low) / self.low
        self.jCache_open2close = (self.open - self.close) / self.close
        pass

    def set_up_with_dic(self, **kwargs):
        for k, v in kwargs.items():
            # do not replace the keys in this instance
            if not hasattr(self, k) or not getattr(self, k):
                setattr(self, k, v)

    @classmethod
    def select_stkcode(cls, stkcode):
        sf_dic = SelfFund.select_stk_from_mongo(stkcode, tb_name=mongoManager.COL_SELF_FUND)
        if sf_dic:
            sfund = SelfFund()
            sfund.set_up_with_dic(**sf_dic)
        else:
            base_data = BaseData.select_stk_from_mongo(stkcode)
            sfund = SelfFund()
            if base_data:
                sfund.set_up_with_dic(**base_data)
            else:
                raise Exception('代码%s不在本地数据库中'%stkcode)
        return sfund

    def add_score(self, score):
        self.score += score
        return self

    def update_realtime_data(self, data):
        if isinstance(data, dict):
            for k, v in data.items():
                if hasattr(self, k):
                    setattr(self, k, v)
            self.caculate_cur_day()
            self.saveToMongo(tb_name=mongoManager.COL_SELF_FUND)
        return self

    # 是否高开
    def is_open_high(self, throld=0):
        if self.close == 0:
            return False
        return (self.open - self.close)/self.close >= throld

    # 是否高走
    def is_high_walk(self, throld=0):
        if self.open == 0:
            return False
        return (self.now - self.open)/self.open >= throld

    # 是否反转 反转超过跌幅60%当做反转
    def is_reverse(self, low_dis=0.02 ,throld=0):
        return

    # 是否突破
    def is_run_out_price(self, price, throld=0):
        if self.now == 0:
            return False
        return (self.now - price)/price >= throld

    # 是否高于MD5
    def is_high_md5(self):
        #获取MD5
        return

    def predict_data_source(self, to_csv=False, ktype='D', append_now=True):
        from emutils import series_utils
        from LSTM.variableset import EMVar, EMPath
        try:
            df = self.get_index(start='2010-01-01', ktype=ktype, insertdb=False, append_now=append_now)

            df[EMVar.label] = series_utils.data_offset(df[EMVar.close], offset=-1)
            df[EMVar.dm1_close] = series_utils.data_offset(df[EMVar.close], offset=1)
            df[EMVar.dm2_close] = series_utils.data_offset(df[EMVar.close], offset=2)
            df[EMVar.dm1_ajx_price] = (df[EMVar.close] - df[EMVar.dm1_close]) / df[EMVar.dm1_close]
            df[EMVar.dm2_ajx_price] = (df[EMVar.dm1_close] - df[EMVar.dm2_close]) / df[EMVar.dm1_close]

            INPUT_COLUMNS = EMVar.BASE_COLUMNS + [EMVar.dm1_close, EMVar.dm1_ajx_price, EMVar.dm2_close, EMVar.dm2_ajx_price]
            OUTPUT_COLUMNS = [EMVar.label]

            output_df = df[INPUT_COLUMNS + OUTPUT_COLUMNS]
            if to_csv:
                df.to_csv(path_or_buf=EMPath.data_file_full_path(code=self.stkcode, fileName=('%s_%s.csv' % (self.stkcode, ktype))),
                          columns=INPUT_COLUMNS + OUTPUT_COLUMNS, index=False)
            return {'data': output_df,'inputData': len(INPUT_COLUMNS),"outputData": len(OUTPUT_COLUMNS)}
        except Exception as e:
            print('%s 历史数据获取失败' % self.stkcode)
            print(e)
            return None
        return

    def get_index(self, start=None, end=datetime.datetime.now().strftime(EMVar.FORMATE_YMD_HMS), precount=0, count=0,
                  ktype='D', indicator='ma', append_now=True, insertdb=False, autype='qfq'):

        return self.__get_index(start=start, end=end, precount=precount, count=count, ktype=ktype,
                                isindex=False, indicator=indicator, append_now=append_now, insertdb=insertdb, autype=autype)

    def get_market_index(self, is_total=False, ktype='D'):
        df1 = None
        df2 = None
        df3 = None
        df4 = None
        if self.stkcode.startswith('6') or is_total:#获取对应市场
            df1 = self.__get_market_cache_index(index_code=EMVar.CODE_SH, name='上证指数', ktype=ktype)
        if self.stkcode.startswith('3') or is_total:#获取对应市场
            df2 = self.__get_market_cache_index(index_code=EMVar.CODE_CY, name='创业板指', ktype=ktype)
        if self.stkcode.startswith('000') or is_total:#获取对应市场
            df3 = self.__get_market_cache_index(index_code=EMVar.CODE_SZ, name='深圳成指', ktype=ktype)
        if self.stkcode.startswith('002') or is_total:#获取对应市场
            df4 = self.__get_market_cache_index(index_code=EMVar.CODE_ZX, name='中小指', ktype=ktype)
        if is_total:
            datas = pd.concat([df1,df2,df3,df4]).sort_values(EMVar.date)
            return datas
        else:
            return df1 or df2 or df3 or df4
        pass

    def __get_market_cache_index(self, index_code, name, ktype='D'):
        key = '%s_%s'%(index_code, ktype)
        if not key in index_date_cache:
            sfund = SelfFund(stkcode=index_code, stkname=name)
            df = sfund.__get_index(start='2005-01-01', isindex=True, ktype=ktype)
            index_date_cache[key] = df
        return index_date_cache[key]


    '''
    precount: 当前结束时间周期的偏移数，只能是负数
    count : 当前结束时间周期偏移数之前多少个周期的数据, 0 表示开始日期跟结束日期一样
    '''
    def __get_index(self, start=None, end=datetime.datetime.now().strftime(EMVar.FORMATE_YMD_HMS), precount=0, count=0, ktype='D', isindex=False, indicator='ma', append_now=True, insertdb=False, autype='qfq'):
        if precount>0:
            raise Exception('无法获取未来数据')

        end = self.__end_date(end, precount, ktype)

        start = start or self.__start_date(end, count, ktype)
        # 标准的基础数据， index是datetime
        # 最后缓存的是历史记录，当天的数据不作为缓存，会定时添加到缓存的dataframe中
        data = self.__get_data(start, end, ktype,insertdb,autype,isindex)
        # 指标类数据不需要再加入最新的data，因为一直都是拿实时的
        if append_now and not isindex:
            # and datetime.datetime.now().strftime(EMVar.FORMATE_YMD) not in data.index
            str_last_work_day = time_utils.get_last_work_day(formatter=EMVar.FORMATE_YMD)
            # data[str_last_work_day].shape[0] != 1 这种判断会报 key error的错误
            if ktype == 'D' and str_last_work_day not in data.index :
                new_day_data = self.new_day_data()
                data.ix[parse(str_last_work_day)] = new_day_data
            # 判断当前最后的一个工作日是不是所有都已经在里面，因为是60min，所以shape 是 4
            if ktype == '60' and (str_last_work_day not in data.index or data[str_last_work_day].shape[0] != 4):
                # 暂时先将现在的删除掉
                if str_last_work_day in data.index:
                    data.drop(data[str_last_work_day].index)
                    r = mongoManager.db[mongoManager.COL_STK_HIS_DATA].delete_many({EMVar.code:self.stkcode,EMVar.date:{'$gte':str_last_work_day}, EMVar.ktype:'60'})
                new_60min_data = self.new_60min_data()
                for x in list(new_60min_data.index):
                    # if x not in data.index:
                    #  不覆盖的话就会导致无法更新到最新的数据，导致最后面的判断错误
                    data.loc[x] = new_60min_data.loc[x]

        data[EMVar.dm1_close] = data[EMVar.close].shift(1)
        data[EMVar.dm1_ajx_price] = (data[EMVar.close] - data[EMVar.dm1_close])/ data[EMVar.dm1_close]
        if indicator:
            if not isinstance(indicator, list):
                indicator = [indicator]
            for i in indicator:
                if hasattr(self, i):
                    self.__detect_df_correct(data)
                    func_method = getattr(self, i)
                    data = func_method(data)
        return data

    def shape(self, df):
        df[EMVar.ABS_CLOSE2OPEN] = np.abs((df[EMVar.close] - df[EMVar.open])/df[EMVar.open])
        df[EMVar.ABS_HIGH2LOW] = np.abs((df[EMVar.high] - df[EMVar.low]) / df[EMVar.low])
        return df

    def ma(self, df):
        df[EMVar.MA5] = abstract.Function('ma', df, price='close', timeperiod=5).outputs
        df[EMVar.MA10] = abstract.Function('ma', df, price='close', timeperiod=10).outputs
        df[EMVar.MA20] = abstract.Function('ma', df, price='close', timeperiod=20).outputs
        df[EMVar.MA30] = abstract.Function('ma', df, price='close', timeperiod=30).outputs
        df[EMVar.MA60] = abstract.Function('ma', df, price='close', timeperiod=60).outputs
        return df

    def macd(self,df):
        macd = abstract.Function('macd', df, price='close').outputs
        df = pd.merge_asof(df, macd, left_index=True, right_index=True)
        return df

    def max(self, df):
        df[EMVar.max] = abstract.Function('max', df, price='close', timeperiod=5).outputs
        return df

    def adosc(selfs, df):
        df[EMVar.adosc] = abstract.Function('adosc', df).outputs
        return df



    def __detect_df_correct(self,df):
        if not isinstance(df, pd.DataFrame):
            raise Exception('计算Shape方法， data不为pandas')
        if EMVar.close not in df.columns or EMVar.open not in df.columns:
            raise Exception('计算Shape方法， OPEN/CLOSE 字段不存在')
        if EMVar.high not in df.columns or EMVar.low not in df.columns:
            raise Exception('计算Shape方法， HIGH/LOW 字段不存在')



    def new_day_data(self):
        if not self.is_real_time_valid():
            self.update_from_ts()
        last_work_day = time_utils.get_last_work_day(formatter=EMVar.FORMATE_YMD)
        d = {EMVar.code:self.stkcode, EMVar.high:self.high, EMVar.low:self.low, EMVar.open:self.open, EMVar.close:self.now,
             EMVar.ktype:'D',EMVar.date:self.__date, EMVar.lastupdate_time:EMVar.STR_NOW_YMD_HMS , EMVar.volume:float(self.volume)/100}
        mongoManager.db[mongoManager.COL_STK_HIS_DATA].update_one({
            EMVar.code: self.stkcode, EMVar.date: self.__date, EMVar.ktype: 'D'}, {'$set': d},upsert=True)
        return pd.Series(d)

    def new_60min_data(self):

        #   拿到 tushare 5分钟序列
        data = ts.get_k_data(code=self.stkcode, ktype='5')
        time_series = pd.to_datetime(data['date'])
        data.set_index(time_series, inplace=True)
        # EMVar.STR_NOW_YMD
        # .ix[:,[EMVar.open, EMVar.high, EMVar.low, EMVar.close,EMVar.volume]]
        # cur_day_5min_df = data['2017-09-08 09:30:00':'2017-09-08 13:45:00']
        # 拿最后的一个工作日,切割 出 当天5分钟序列
        if time_utils.get_last_work_day(formatter=EMVar.FORMATE_YMD) in data.index:
            cur_day_5min_df = data[time_utils.get_last_work_day(formatter=EMVar.FORMATE_YMD)]
            cur_day_5min_df.apply(pd.to_numeric, errors='ignore')
            conversion = {'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last', 'volume': 'sum'}
            # 重新采样当天5分钟 为 1小时
            moring = cur_day_5min_df.between_time(start_time='09:30:00', end_time='11:30:00', include_end=True)
            moring_data = moring.resample('60min', base=30, label='right', closed='right').apply(conversion)

            afternoon = cur_day_5min_df.between_time(start_time='13:00:00', end_time='15:00:00', include_end=True)
            if not afternoon.empty:
                afternoon_data = afternoon.resample('60min', base=60, label='right', closed='right').apply(conversion)
                total_data = moring_data.append(afternoon_data)
            else:
                total_data = moring_data
            total_data['code'] = self.stkcode
            total_data['date'] = list(pd.Series(total_data.index).dt.strftime(EMVar.FORMATE_YMD_HM))
            ktype_60 = '60'
            total_data[EMVar.lastupdate_time] = EMVar.STR_NOW_YMD_HMS
            total_data[EMVar.ktype] = ktype_60
            for index, item in total_data.iterrows():
                mongoManager.db[mongoManager.COL_STK_HIS_DATA].update_one({
                    EMVar.code: self.stkcode, EMVar.date: item[EMVar.date], EMVar.ktype: ktype_60}, {'$set': dict(item)},upsert=True)
            return total_data
        else:
            return pd.DataFrame()




    def __end_date(self, end=datetime.datetime.now().strftime(EMVar.FORMATE_YMD_HMS), precount=0, ktype='D'):
        return end
        pass

    def __start_date(self, end=datetime.datetime.now().strftime(EMVar.FORMATE_YMD_HMS), count=0, ktype='D'):
        # 统一只 拿到 日这个周期
        return None
        pass

    def __get_data(self, start=None, end=None, ktype=None, insertdb=True, autype='qfq', isindex=False):
        # 根据 ktype 判断用哪个函数获取数据
        if ktype == 'D':
            if self.stkcode not in day_cache.keys():
                day_cache[self.stkcode] = self.__get_data_day(start, end, ktype, insertdb, autype, isindex)
            return day_cache[self.stkcode]
        if ktype == '60':
            key = self.stkcode+'_'+ktype
            if key not in min_cache.keys():
                min_cache[key] = self.__get_data_60min(start, end, ktype, insertdb, isindex)
            return min_cache[key]


    def __get_data_day(self, start=None, end=None,ktype=None, insertdb=True,autype='qfq',isindex=False):

        def is_local_data_new(start_delta, end_delta,last_update_delta):
            if last_update_delta[EMVar.DAY] <= 0 or end_delta[EMVar.DAY] <=0:
                # 表示不需要从Tushare中同步
                # return True
                return False
            return False

        return self.__sys_local_or_web_data(start, end, ktype,insertdb, is_local_data_new, autype, filterdatas=None, isindex=isindex)

    def __get_data_60min(self, start=None, end=None,ktype=None, insertdb=True, autype=None,isindex=False):
        def is_local_data_new(start_delta, end_delta,last_update_delta):
            if last_update_delta[EMVar.MIN] <= 30 or end_delta[EMVar.MIN] <= 30:
                # 表示不需要从Tushare中同步
                # return True
                return False
            return False

        def filter_datas(df):
            if not isinstance(df, pd.DataFrame) :
                return
            last_work_day = time_utils.get_last_work_day(formatter=EMVar.FORMATE_YMD)
            if last_work_day in df.index:
                df = df.drop(df[last_work_day].index)
            return df

        return self.__sys_local_or_web_data(start, end, ktype, insertdb, is_local_data_new, autype, filter_datas, isindex)

    def __sys_local_or_web_data(self, start, end, ktype, insertdb, is_local_data_new, autype='qfq', filterdatas=None, isindex=False):
        need_ts, data = self.__sys_local_data(start, end, ktype, is_local_data_new)
        # 指标类数据还是直接从tushare中获取
        if (need_ts and not data) or isindex:
            ts_data = ts.get_k_data(code=self.stkcode, start=start, end=end, ktype=ktype, autype=autype)
            ts_data = ts_data[EMVar.BASE_COLUMNS]
        else:
            ts_data = pd.DataFrame(data)[EMVar.BASE_COLUMNS]

        # ts_data = ts.get_k_data(code=self.stkcode, start=start, end=end, ktype=ktype)
        # ts_data = ts_data[EMVar.BASE_COLUMNS]

        date_series = pd.to_datetime(ts_data[EMVar.date])
        # date_series = ts_data[EMVar.date].apply(lambda x: parse(x))
        ts_data.set_index(date_series, inplace=True)
        ts_data[EMVar.lastupdate_time] = EMVar.STR_NOW_YMD_HMS
        ts_data[EMVar.ktype] = ktype
        # ts_data[EMVar.date] = ts_data[EMVar.date].apply(parse)
        t1 = datetime.datetime.now()

        if filterdatas:
            ts_data = filterdatas(ts_data)
        if insertdb and need_ts:
            for index, item in ts_data.iterrows():
                mongoManager.db[mongoManager.COL_STK_HIS_DATA].update_one(
                    {EMVar.code: self.stkcode, EMVar.date: item[EMVar.date], EMVar.ktype: ktype},
                    {'$set': dict(item)}, upsert=True)
            d = (datetime.datetime.now() - t1).total_seconds()
            print('INSERT SEPNED TIME %s' % d)

        return ts_data


    # def __is_local_data_new_day(self, start_delta, end_delta,last_update_delta):
    #     pass
    #
    # def __is_local_data_new_min(self, start_delta, end_delta,last_update_delta):
    #
    #     pass

    '''
    获取本地或网络数据
    '''
    def __sys_local_data(self, start=None, end=None, ktype=None, delta_in_range=None):
        cursor = mongoManager.db[mongoManager.COL_STK_HIS_DATA].find({EMVar.code: self.stkcode, 'ktype': ktype}).sort(EMVar.date, pymongo.ASCENDING)

        if isinstance(start, str):
            start = parse(start)
        if isinstance(end, str):
            end = parse(end)

        if cursor and cursor.count():
            l = list(cursor)
            startTime = l[0][EMVar.date]
            lastTime = l[-1][EMVar.date]
            lastUpdateTime = l[-1][EMVar.lastupdate_time]
            delta_start = time_utils.delta_date(start, parse(startTime))
            delta_last = time_utils.delta_date(end, parse(lastTime))
            delta_last_updateTime = time_utils.delta_date(end, parse(lastUpdateTime))
            if delta_in_range and delta_in_range(delta_start, delta_last, delta_last_updateTime):
                return (False, l)
            else:
                return True, None
        else:
            return True, None


